package org.anderground.editor.edits;

import static org.anderground.editor.edits.Editor.edit;

import java.util.ArrayList;
import java.util.List;

import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEdit;

import org.anderground.core.model.Line;
import org.anderground.core.model.ModelData;
import org.anderground.core.model.ModelException;
import org.anderground.core.model.Station;
import org.anderground.core.model.Transition;
import org.anderground.core.model.ModelData.OnAddCallback;

/**
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 * 
 */
public class AddDataAction extends DataAction {

	/**
	 * The constructor.
	 * 
	 * @param data
	 * @param stations
	 *            the stations to add, may be null
	 * @param transitions
	 *            the transitions to add, may be null
	 */
	public AddDataAction(ModelData data, Station[] stations,
			Transition[] transitions) {
		super(data, stations, transitions);
	}

	/**
	 * The constructor.
	 * 
	 * @param data
	 * @param stations
	 *            the stations to add, may be null
	 * @param transitions
	 *            the transitions to add, may be null
	 */
	public AddDataAction(ModelData data, List<? extends Station> stations,
			List<? extends Transition> transitions) {
		this(data, stations == null ? null : (Station[]) stations
				.toArray(new Station[stations.size()]),
				transitions == null ? null : (Transition[]) transitions
						.toArray(new Transition[transitions.size()]));
	}

	/**
	 * The constructor.
	 * 
	 * @param data
	 * @param stations
	 *            the stations to add
	 */
	public AddDataAction(ModelData data, Station... stations) {
		this(data, stations, null);
	}

	/**
	 * The constructor.
	 * 
	 * @param data
	 * @param transition
	 *            the transitions to add
	 */
	public AddDataAction(ModelData data, Transition... transitions) {
		this(data, null, transitions);
	}

	@Override
	UndoableEdit perform() throws ActionException {
		try {
			return new AddDataEdit(getData(), getStations(), getTransitions(),
					getLines());
		} catch (ModelException e) {
			throw new ActionException(e);
		}
	}

	/**
	 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
	 */
	@SuppressWarnings("serial")
	private static final class AddDataEdit extends DataEdit {

		/**
		 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
		 */
		private class AtomicAddDataEdit extends AtomicDataEdit {

			/** list of stations had been affected */
			private List<Station> addedStations = new ArrayList<Station>();

			/** list of transitions had been affected */
			private List<Transition> addedTransitions = new ArrayList<Transition>();

			/** this listener will form the modification object */
			private OnAddCallback callback = new OnAddCallback() {

				@Override
				public void onAdd(Station station) {
					addedStations.add(station);
				}

				@Override
				public void onAdd(Transition transition) {
					addedTransitions.add(transition);
				}

			};

			/**
			 * 
			 * @param station
			 * @throws ModelException
			 */
			public AtomicAddDataEdit(Station station) throws ModelException {
				edit(getData()).add(station, callback);
			}

			/**
			 * 
			 * @param transition
			 * @throws ModelException
			 */
			public AtomicAddDataEdit(Transition transition)
					throws ModelException {
				edit(getData()).add(transition, callback);
			}

			@Override
			public void undo() throws CannotUndoException {
				super.undo();
				try {
					for (Transition transition : addedTransitions) {
						edit(getData()).remove(transition, null);
					}
					for (Station station : addedStations) {
						edit(getData()).remove(station, null);
					}
				} catch (ModelException e) {
					class MyCannotUndoException extends CannotUndoException {
						public MyCannotUndoException(ModelException e) {
							initCause(e);
						}
					}
					throw new MyCannotUndoException(e);
				}
			}

			@Override
			public void redo() throws CannotRedoException {
				super.redo();
				try {
					for (Station station : addedStations) {
						edit(getData()).add(station, null);
					}
					for (Transition transition : addedTransitions) {
						edit(getData()).add(transition, null);
					}
				} catch (ModelException e) {
					class MyCannotRedoException extends CannotRedoException {
						public MyCannotRedoException(ModelException e) {
							initCause(e);
						}
					}
					throw new MyCannotRedoException(e);
				}
			}

		}

		public static final String PRESENTATION_NAME = "Adding objects";

		/**
		 * The constructor.
		 * 
		 * @param data
		 * @param stations
		 *            the stations to add, may be null
		 * @param transitions
		 *            the transitions to add, may be null
		 * @throws ModelException
		 */
		public AddDataEdit(ModelData data, Station[] stations,
				Transition[] transitions, Line[] lines) throws ModelException {
			super(data);
			if (stations != null) {
				for (int i = 0; i < stations.length; i++) {
					if (stations[i] != null) {
						addEdit(new AtomicAddDataEdit(stations[i]));
					}
				}
			}
			if (transitions != null) {
				for (int i = 0; i < transitions.length; i++) {
					if (transitions[i] != null) {
						addEdit(new AtomicAddDataEdit(transitions[i]));
					}
				}
			}
			end();
		}

		@Override
		public String getPresentationName() {
			return PRESENTATION_NAME;
		}

	}

}
